<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>

                    <h4>装饰器</h4>
                    <div class="x-wiki-info"><span>2215次阅读</span></div>
                    <hr style="border-top-color:#ccc" />
                    <div class="x-wiki-content x-content"><p>由于函数也是一个对象，而且函数对象可以被赋值给变量，所以，通过变量也能调用该函数。</p>
<pre><code>&gt;&gt;&gt; def now():
...     print &#39;2013-12-25&#39;
...
&gt;&gt;&gt; f = now
&gt;&gt;&gt; f()
2013-12-25
</code></pre><p>函数对象有一个<code>__name__</code>属性，可以拿到函数的名字：</p>
<pre><code>&gt;&gt;&gt; now.__name__
&#39;now&#39;
&gt;&gt;&gt; f.__name__
&#39;now&#39;
</code></pre><p>现在，假设我们要增强<code>now()</code>函数的功能，比如，在函数调用前后自动打印日志，但又不希望修改<code>now()</code>函数的定义，这种在代码运行期间动态增加功能的方式，称之为“装饰器”（Decorator）。</p>
<p>本质上，decorator就是一个返回函数的高阶函数。所以，我们要定义一个能打印日志的decorator，可以定义如下：</p>
<pre><code>def log(func):
    def wrapper(*args, **kw):
        print &#39;call %s():&#39; % func.__name__
        return func(*args, **kw)
    return wrapper
</code></pre><p>观察上面的<code>log</code>，因为它是一个decorator，所以接受一个函数作为参数，并返回一个函数。我们要借助Python的@语法，把decorator置于函数的定义处：</p>
<pre><code>@log
def now():
    print &#39;2013-12-25&#39;
</code></pre><p>调用<code>now()</code>函数，不仅会运行<code>now()</code>函数本身，还会在运行<code>now()</code>函数前打印一行日志：</p>
<pre><code>&gt;&gt;&gt; now()
call now():
2013-12-25
</code></pre><p>把<code>@log</code>放到<code>now()</code>函数的定义处，相当于执行了语句：</p>
<pre><code>now = log(now)
</code></pre><p>由于<code>log()</code>是一个decorator，返回一个函数，所以，原来的<code>now()</code>函数仍然存在，只是现在同名的now变量指向了新的函数，于是调用<code>now()</code>将执行新函数，即在<code>log()</code>函数中返回的<code>wrapper()</code>函数。</p>
<p><code>wrapper()</code>函数的参数定义是<code>(*args, **kw)</code>，因此，<code>wrapper()</code>函数可以接受任意参数的调用。在<code>wrapper()</code>函数内，首先打印日志，再紧接着调用原始函数。</p>
<p>如果decorator本身需要传入参数，那就需要编写一个返回decorator的高阶函数，写出来会更复杂。比如，要自定义log的文本：</p>
<pre><code>def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print &#39;%s %s():&#39; % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator
</code></pre><p>这个3层嵌套的decorator用法如下：</p>
<pre><code>@log(&#39;execute&#39;)
def now():
    print &#39;2013-12-25&#39;
</code></pre><p>执行结果如下：</p>
<pre><code>&gt;&gt;&gt; now()
execute now():
2013-12-25
</code></pre><p>和两层嵌套的decorator相比，3层嵌套的效果是这样的：</p>
<pre><code>&gt;&gt;&gt; now = log(&#39;execute&#39;)(now)
</code></pre><p>我们来剖析上面的语句，首先执行<code>log(&#39;execute&#39;)</code>，返回的是<code>decorator</code>函数，再调用返回的函数，参数是<code>now</code>函数，返回值最终是<code>wrapper</code>函数。</p>
<p>以上两种decorator的定义都没有问题，但还差最后一步。因为我们讲了函数也是对象，它有<code>__name__</code>等属性，但你去看经过decorator装饰之后的函数，它们的<code>__name__</code>已经从原来的<code>&#39;now&#39;</code>变成了<code>&#39;wrapper&#39;</code>：</p>
<pre><code>&gt;&gt;&gt; now.__name__
&#39;wrapper&#39;
</code></pre><p>因为返回的那个<code>wrapper()</code>函数名字就是<code>&#39;wrapper&#39;</code>，所以，需要把原始函数的<code>__name__</code>等属性复制到<code>wrapper()</code>函数中，否则，有些依赖函数签名的代码执行就会出错。</p>
<p>不需要编写<code>wrapper.__name__ = func.__name__</code>这样的代码，Python内置的<code>functools.wraps</code>就是干这个事的，所以，一个完整的decorator的写法如下：</p>
<pre><code>import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print &#39;call %s():&#39; % func.__name__
        return func(*args, **kw)
    return wrapper
</code></pre><p>或者针对带参数的decorator：</p>
<pre><code>import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print &#39;%s %s():&#39; % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator
</code></pre><p><code>import functools</code>是导入<code>functools</code>模块。模块的概念稍候讲解。现在，只需记住在定义<code>wrapper()</code>的前面加上<code>@functools.wraps(func)</code>即可。</p>
<h3 id="-">小结</h3>
<p>在面向对象（OOP）的设计模式中，decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现，而Python除了能支持OOP的decorator外，直接从语法层次支持decorator。Python的decorator可以用函数实现，也可以用类实现。</p>
<p>decorator可以增强函数的功能，定义起来虽然有点复杂，但使用起来非常灵活和方便。</p>
<p>请编写一个decorator，能在函数调用的前后打印出<code>&#39;begin call&#39;</code>和<code>&#39;end call&#39;</code>的日志。</p>
<p>再思考一下能否写出一个<code>@log</code>的decorator，使它既支持：</p>
<pre><code>@log
def f():
    pass
</code></pre><p>又支持：</p>
<pre><code>@log(&#39;execute&#39;)
def f():
    pass
</code></pre></div>

                    <hr style="border-top-color:#ccc" />

                    